Bound WslCoreInstance init transactions - #41385
Bound WslCoreInstance init transactions#41385Sylvain MOLINIER (SylvainM98) wants to merge 3 commits into
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
6137ecb to
d8efbde
Compare
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR threads a configurable socket timeout through init-channel transactions in the Windows service code and adds unit tests to validate transaction timeout and reply handling over SocketChannel.
Changes:
- Add unit tests covering
SocketChannel::StartTransaction(timeout)timeout behavior and successful reply reception. - Update
WslCoreInstanceinit-channel calls to passm_socketTimeoutinto transaction creation and execution. - Ensure process creation transaction uses the configured socket timeout.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| test/windows/UnitTests.cpp | Adds new socket transaction tests for timeout and response behavior. |
| src/windows/service/exe/WslCoreInstance.cpp | Passes m_socketTimeout to init-channel transactions to bound socket operations. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| { | ||
| auto [client, server] = MakeSocketPair(); | ||
| wsl::shared::SocketChannel channel{std::move(client), "client"}; | ||
| auto transaction = channel.StartTransaction(200); |
| const auto start = std::chrono::steady_clock::now(); | ||
| const auto hr = wil::ResultFromException([&]() { transaction.Receive<RESULT_MESSAGE<int32_t>>(); }); | ||
| const auto elapsed = | ||
| std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count(); | ||
|
|
||
| VERIFY_ARE_EQUAL(hr, HRESULT_FROM_WIN32(ERROR_TIMEOUT)); | ||
| VERIFY_IS_GREATER_THAN_OR_EQUAL(elapsed, 100LL); |
| VERIFY_IS_GREATER_THAN_OR_EQUAL(elapsed, 100LL); | ||
| VERIFY_IS_LESS_THAN(elapsed, 5000LL); |
| { | ||
| auto sessionLock = sessionLeader->Lock(); | ||
| port = sessionLeader->GetChannel().Transaction<LX_INIT_CREATE_PROCESS_UTILITY_VM>(messageSpan).Result; | ||
| port = sessionLeader->GetChannel().Transaction<LX_INIT_CREATE_PROCESS_UTILITY_VM>(messageSpan, nullptr, m_socketTimeout).Result; |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (7)
test/windows/UnitTests.cpp:7718
- This test doesn’t actually assert that the
StartTransaction(200)timeout is being honored: it only checkselapsed >= 100ms, so it could still pass if the implementation incorrectly uses a default (e.g., 100ms) and ignores the 200ms parameter. To make the test validate the intended behavior, assert elapsed time against the configured timeout (with a reasonable tolerance), and consider using named constants for the timeout and tolerances to keep the intent clear and reduce flakiness.
auto transaction = channel.StartTransaction(200);
test/windows/UnitTests.cpp:7736
- This test doesn’t actually assert that the
StartTransaction(200)timeout is being honored: it only checkselapsed >= 100ms, so it could still pass if the implementation incorrectly uses a default (e.g., 100ms) and ignores the 200ms parameter. To make the test validate the intended behavior, assert elapsed time against the configured timeout (with a reasonable tolerance), and consider using named constants for the timeout and tolerances to keep the intent clear and reduce flakiness.
const auto start = std::chrono::steady_clock::now();
const auto hr = wil::ResultFromException([&]() { transaction.Receive<RESULT_MESSAGE<int32_t>>(); });
const auto elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count();
VERIFY_ARE_EQUAL(hr, HRESULT_FROM_WIN32(ERROR_TIMEOUT));
VERIFY_IS_GREATER_THAN_OR_EQUAL(elapsed, 100LL);
VERIFY_IS_LESS_THAN(elapsed, 5000LL);
test/windows/UnitTests.cpp:7718
- The newly added tests introduce several hard-coded timing and value constants (e.g., 200, 1000, 100, 5000, 42). Using
constexprconstants (e.g.,kTimeoutMs,kMinElapsedMs,kMaxElapsedMs,kExpectedResult) would make the tests easier to understand and adjust, and would reduce the chance of mismatched expectations if timeouts are tweaked later.
auto transaction = channel.StartTransaction(200);
test/windows/UnitTests.cpp:7736
- The newly added tests introduce several hard-coded timing and value constants (e.g., 200, 1000, 100, 5000, 42). Using
constexprconstants (e.g.,kTimeoutMs,kMinElapsedMs,kMaxElapsedMs,kExpectedResult) would make the tests easier to understand and adjust, and would reduce the chance of mismatched expectations if timeouts are tweaked later.
VERIFY_IS_GREATER_THAN_OR_EQUAL(elapsed, 100LL);
VERIFY_IS_LESS_THAN(elapsed, 5000LL);
test/windows/UnitTests.cpp:7742
- The newly added tests introduce several hard-coded timing and value constants (e.g., 200, 1000, 100, 5000, 42). Using
constexprconstants (e.g.,kTimeoutMs,kMinElapsedMs,kMaxElapsedMs,kExpectedResult) would make the tests easier to understand and adjust, and would reduce the chance of mismatched expectations if timeouts are tweaked later.
auto transaction = channel.StartTransaction(1000);
test/windows/UnitTests.cpp:7757
- The newly added tests introduce several hard-coded timing and value constants (e.g., 200, 1000, 100, 5000, 42). Using
constexprconstants (e.g.,kTimeoutMs,kMinElapsedMs,kMaxElapsedMs,kExpectedResult) would make the tests easier to understand and adjust, and would reduce the chance of mismatched expectations if timeouts are tweaked later.
response.Result = 42;
src/windows/service/exe/WslCoreInstance.cpp:228
- Passing a raw
nullptras the middle argument makes the call site ambiguous (it’s not obvious what parameter is being intentionally omitted). If the API is expecting an optional/cancellation object, prefer an explicit empty value (e.g.,std::nullopt/ default-constructed token) or add a small clarifying comment at the call site so future readers don’t have to look up the overload/signature.
port = sessionLeader->GetChannel().Transaction<LX_INIT_CREATE_PROCESS_UTILITY_VM>(messageSpan, nullptr, m_socketTimeout).Result;
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (2)
test/windows/UnitTests.cpp:7739
elapsedis truncated to whole milliseconds viaduration_cast(...).count(), which can make this assertion flaky (e.g., a ~199.9ms timeout becomes 199ms and fails the>=check). Consider keepingelapsedas astd::chrono::steady_clock::duration(or at least avoid truncation by using microseconds / rounding up) and compare usingstd::chrono::millisecondsdurations directly.
const auto start = std::chrono::steady_clock::now();
const auto hr = wil::ResultFromException([&]() { transaction.Receive<RESULT_MESSAGE<int32_t>>(); });
const auto elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count();
VERIFY_ARE_EQUAL(hr, HRESULT_FROM_WIN32(ERROR_TIMEOUT));
VERIFY_IS_GREATER_THAN_OR_EQUAL(elapsed, static_cast<LONGLONG>(transactionTimeout) - schedulingTolerance);
src/windows/service/exe/WslCoreInstance.cpp:228
- Passing a raw
nullptras the middle argument makes the callsite ambiguous/brittle (it’s not self-documenting what parameter is being omitted). If possible, prefer an overload that takes only(messageSpan, timeout)(or uses a strongly-typed optional/default parameter) so readers don’t have to chase the callee signature; otherwise, add an explicit local variable (e.g.,auto cancellation = /*...*/;) or a short comment indicating whatnullptrrepresents.
port = sessionLeader->GetChannel().Transaction<LX_INIT_CREATE_PROCESS_UTILITY_VM>(messageSpan, nullptr, m_socketTimeout).Result;
|
@microsoft-github-policy-service agree |
| std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count(); | ||
|
|
||
| VERIFY_ARE_EQUAL(hr, HRESULT_FROM_WIN32(ERROR_TIMEOUT)); | ||
| VERIFY_IS_GREATER_THAN_OR_EQUAL(elapsed, static_cast<LONGLONG>(transactionTimeout) - schedulingTolerance); |
There was a problem hiding this comment.
These tests don't seem to exercise the code that this PR changes, although I don't recommend having timing based tests like this, since they will fail if the machine is under pressure
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/windows/service/exe/WslCoreInstance.cpp:405
- PR description/issue notes say UnitTests::SocketChannel was extended with Transaction/StartTransaction timeout cases (peer stays connected without replying => HRESULT_FROM_WIN32(ERROR_TIMEOUT), and peer replying before deadline succeeds). I couldn't find any SocketChannel Transaction/StartTransaction timeout tests in the current test sources (only a ReceiveMessage finite-timeout test in test/windows/UnitTests.cpp around lines 7705-7713). Please add the claimed Transaction timeout coverage or update the PR description accordingly.
auto config = wsl::windows::common::helpers::GenerateConfigurationMessage(
m_configuration.Name, fixedDrives, m_defaultUid, timezone, {}, m_featureFlags, drvfsMount);
auto transaction = m_initChannel->GetChannel().StartTransaction(m_socketTimeout);
transaction.Send<LX_INIT_CONFIGURATION_INFORMATION>(gsl::span(config));
Summary of the Pull Request
SocketChanneldefaults to an infinite timeout when none is supplied. ThreeWslCoreInstanceoperations rely on that default while holding locks needed by shutdown or other session operations. If the guest stays connected but stops responding, those operations can wait indefinitely and preventWslServicefrom making progress.This passes the instance's existing
m_socketTimeoutto all three operations.PR Checklist
Detailed Description of the Pull Request / Additional comments
On Windows,
DefaultSocketTimeoutisINFINITE, so aSocketChanneltransaction that does not receive an explicit timeout can wait without bound.WslCoreInstancealready owns the applicable timeout value:m_socketTimeout, sourced fromDistributionStartTimeout, which defaults to 60 seconds and is configurable through.wslconfigaswsl2.distributionStartTimeout.The class already applies this timeout to comparable operations:
ReceiveMessage(..., m_socketTimeout)Transaction(..., m_socketTimeout)StartTransaction(m_socketTimeout)CreateLinuxProcess(..., m_socketTimeout)Transaction(..., m_socketTimeout)The timeout was omitted from three operations:
CreateLxProcess()WslCoreInstance::m_lockand the session-leader channel lockInitialize()WslCoreInstance::m_lock; its caller holdsLxssUserSessionImpl::m_instanceLockUpdateTimezone()m_instanceLockA process-creation transaction can therefore hold
WslCoreInstance::m_lockindefinitely.RequestStop()andStop()require the same lock, while their callers holdLxssUserSessionImpl::m_instanceLock. This can prevent shutdown and other session operations from progressing.If service or session teardown subsequently enters
ClearSessionsAndBlockNewInstancesLockHeld(), it holdsg_sessionTerminationLockwhile waiting for shutdown. New COM session activation can then also stop progressing.This change passes
m_socketTimeoutto the three omitted operations. When the timeout expires, the affected operation fails, unwinds, and releases its locks. It does not change the timeout value or introduce a new policy.This removes three unbounded waits. It does not address other independent causes of guest or service instability. Other unbounded
m_miniInitChanneloperations inWslCoreVmare intentionally left out because each requires a separate timeout and cancellation analysis.Validation Steps Performed
Extended
UnitTests::SocketChannelwith two cases:HRESULT_FROM_WIN32(ERROR_TIMEOUT). The elapsed time is checked to verify that the configured deadline was honored rather than the operation returning immediately.Static inspection confirmed that the test helpers and assertions are available in the existing test scope.
The Windows build and tests were not run locally. Build,
clang-format, and test validation are pending repository CI.